<template>
  <div
    class="picker"
    @touchstart="onStart"
    @touchmove="onMove"
    @touchend="onEnd"
    ref="rootRef"
  >
    <div class="picker-mask" ref="maskRef"></div>
    <div class="picker-indicator" ref="indicatorRef"></div>
    <div class="picker-content" ref="contentRef">
      <div :key="item.code" v-for="item in children" class="picker-item">
        {{ item.name }}
      </div>
    </div>
  </div>
</template>

<script>
const Velocity = ((minInterval = 30, maxInterval = 100) => {
  let _time = 0;
  let _y = 0;
  let _velocity = 0;
  const recorder = {
    record: (y) => {
      const now = +new Date();
      _velocity = (y - _y) / (now - _time);
      if (now - _time >= minInterval) {
        _velocity = now - _time <= maxInterval ? _velocity : 0;
        _y = y;
        _time = now;
      }
    },
    getVelocity: (y) => {
      if (y !== _y) {
        recorder.record(y);
      }
      return _velocity;
    },
  };
  return recorder;
})();
export default {
  data() {
    return {
      itemHeight: 0,
      scrollValue: 0,
      scrollY: -1,
      lastY: 0,
      startY: 0,
      scrollDisabled: false,
      isMoving: false,

      selectedValue: 0,
    };
  },
  props: {
    noAnimate: Boolean,
    disabled: Boolean,
    children: {
      type: Array,
      default: [1, 2, 3, 4, 5, 6, 7],
    },
  },
  watch: {
    children(val) {
      this.scrollTo(0, 0, 0);
    },
  },
  mounted() {
    this.init();
  },
  methods: {
    init() {
      const { contentRef, indicatorRef, maskRef, rootRef } = this.$refs;
      const rootHeight = rootRef.getBoundingClientRect().height;
      const itemHeight = (this.itemHeight = indicatorRef.getBoundingClientRect().height);
      let num = Math.floor(rootHeight / itemHeight);
      if (num % 2 === 0) {
        num--;
      }
      num--;
      num /= 2;
      contentRef.style.padding = `${itemHeight * num}px 0`;
      indicatorRef.style.top = `${itemHeight * num}px`;
      maskRef.style.backgroundSize = `100% ${itemHeight * num}px`;
      this.setDisabled(this.disabled);
      this.$emit("select", {
        selectedValue: this.selectedValue,
        itemHeight: this.itemHeight,
        scrollTo: this.scrollTo,
      });
    },
    onStart(e) {
      const { pageY } = e.touches[0];
      if (this.scrollDisabled) {
        return;
      }
      this.isMoving = true;
      this.startY = pageY;
      this.lastY = this.scrollY;
    },
    onMove(e) {
      const { pageY } = e.touches[0];
      if (this.scrollDisabled || !this.isMoving) {
        return;
      }
      this.scrollY = this.lastY - pageY + this.startY;

      Velocity.record(this.scrollY);

      this.onScrollChange();
      this.setTransform(
        this.$refs.contentRef.style,
        `translate3d(0,${-this.scrollY}px,0)`
      );
    },
    onEnd(e) {
      this.onFinish();
    },

    onFinish() {
      this.isMoving = false;
      let targetY = this.scrollY;
      //高度有子组件决定
      const height = (this.children.length - 1) * this.itemHeight;

      let time = 0.3;

      const velocity = Velocity.getVelocity(targetY) * 4;

      if (velocity) {
        targetY = velocity * 40 + targetY;
        time = Math.abs(velocity) * 0.1;
      }
      if (targetY % this.itemHeight !== 0) {
        targetY = Math.round(targetY / this.itemHeight) * this.itemHeight;
      }
      if (targetY < 0) {
        targetY = 0;
      } else if (targetY > height) {
        targetY = height;
      }

      this.scrollTo(0, targetY, time < 0.3 ? 0.3 : time);
      this.onScrollChange();
    },
    setTransition(nodeStyle, value) {
      nodeStyle.transition = value;
      nodeStyle.webkitTransition = value;
    },
    setTransform(nodeStyle, value) {
      nodeStyle.transform = value;
      nodeStyle.webkitTransform = value;
    },
    setDisabled(disabled = false) {
      this.scrollDisabled = disabled;
    },
    onScrollChange() {
      const top = this.getValue();
      if (top >= 0) {
        const index = this.computeChildIndex(
          top,
          this.itemHeight,
          this.children.length
        );
        if (this.scrollValue !== index) {
          this.scrollValue = index;
          const child = this.children[index];
          this.$emit("onScrollChange", child);
        }
      }
    },

    computeChildIndex(top, itemHeight, childrenLength) {
      const index = Math.round(top / itemHeight);
      return Math.min(index, childrenLength - 1);
    },

    scrollingComplete() {
      const top = this.getValue();
      if (top >= 0) {
        this.doScrollingComplete(top, this.itemHeight, this.children.length);
      }
    },
    doScrollingComplete(top, itemHeight, childrenLength) {
      const index = this.computeChildIndex(top, itemHeight, childrenLength);
      const child = this.children[index];
      if (child) {
        this.selectedValue = child;
        this.$emit("onScrollComplete", child);
      }
    },
    getValue() {
      return this.scrollY;
    },
    scrollTo(_x, y, time = 0.3) {
      if (this.scrollY !== y) {
        this.scrollY = y;
        if (time && this.noAnimate) {
          this.setTransition(
            this.$refs.contentRef.style,
            `cubic-bezier(0,0,0.2,1.15) ${time}s`
          );
        }
        this.setTransform(
          this.$refs.contentRef.style,
          `translate3d(0,${-y}px,0)`
        );
        let self = this;
        setTimeout(() => {
          self.scrollingComplete();
          if (self.$refs.contentRef) {
            self.setTransition(self.$refs.contentRef.style, "");
          }
        }, +time * 1000);
      }
    },
  },
};
</script>

<style lang="scss" scoped>
.picker-item {
  font-size: 16px;
  height: 34px;
  line-height: 34px;
  padding: 0 10px;
  white-space: nowrap;
  position: relative;
  overflow: hidden;
  text-overflow: ellipsis;
  color: #9b9b9b;
  width: 100%;
  box-sizing: border-box;
}

.picker {
  height: 238px;
  display: block;
  position: relative;
  overflow: hidden;
  width: 100%;
  flex: 1;
  text-align: center;
  background: #fff;
}

.picker-mask {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  margin: 0 auto;
  width: 100%;
  z-index: 3;
  background-image: linear-gradient(
      to bottom,
      rgba(255, 255, 255, 0.95),
      rgba(255, 255, 255, 0.6)
    ),
    linear-gradient(to top, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.6));
  background-position: top, bottom;
  background-size: 100% 204px;
  background-repeat: no-repeat;
}

.picker-content {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  z-index: 1;
}

.picker-indicator {
  box-sizing: border-box;
  width: 100%;
  height: 34px;
  position: absolute;
  left: 0;
  top: 102px;
  z-index: 3;
  border-top: 1px solid #ddd;
  border-bottom: 1px solid #ddd;
}
</style>